// Yang Shuai  Copyright (c) 2022 https://yangbuyi.top.
// Copyright (c) 2022. Yangbuyi, personal projects are not allowed to be commercialized without permission.
// Please keep the information of the original author of the code. Thank you

package top.yangbuyi.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.arcsoft.face.*;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import com.arcsoft.face.toolkit.ImageInfo;
import com.google.common.collect.Lists;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import top.yangbuyi.dto.FaceUserInfo;
import top.yangbuyi.dto.ProcessInfo;
import top.yangbuyi.factory.FaceEngineFactory;
import top.yangbuyi.mapper.SysUserFaceInfoMapper;
import top.yangbuyi.service.FaceEngineService;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;


/**
 * @author yangbuyi.top
 */
@Service
public class FaceEngineServiceImpl implements FaceEngineService {

    public final static Logger logger = LoggerFactory.getLogger(FaceEngineServiceImpl.class);

    @Value("${config.sdk-lib-path}")
    public String sdkLibPath;

    @Value("${config.app-id}")
    public String appId;

    @Value("${config.sdk-key}")
    public String sdkKey;

    @Value("${config.thread-pool-size}")
    public Integer threadPoolSize;

    private static FaceEngine faceEngine;

    /**
     * 相似度
     */
    private final Integer passRate = 95;

    private ExecutorService executorService;

    @Autowired
    private SysUserFaceInfoMapper userFaceInfoMapper;

    /**
     * 线程池
     */
    private GenericObjectPool<FaceEngine> faceEngineObjectPool;


    @PostConstruct
    public void init () {
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(threadPoolSize);
        poolConfig.setMaxTotal(threadPoolSize);
        poolConfig.setMinIdle(threadPoolSize);
        poolConfig.setLifo(false);

        // 引擎配置
        EngineConfiguration engineConfiguration = new EngineConfiguration();
        // 检测模式，如果是连续帧的视频流，那么改成VIDEO模式 否则为 图片模式
        engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
        // 人脸检测角度 -> 人脸检测角度，逆时针0度
        engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);

        //功能配置
        FunctionConfiguration functionConfiguration = new FunctionConfiguration();
        functionConfiguration.setSupportAge(true);
        functionConfiguration.setSupportFace3dAngle(true);
        functionConfiguration.setSupportFaceDetect(true);
        functionConfiguration.setSupportFaceRecognition(true);
        functionConfiguration.setSupportGender(true);
        functionConfiguration.setSupportLiveness(true);
        functionConfiguration.setSupportIRLiveness(true);
        engineConfiguration.setFunctionConfiguration(functionConfiguration);

        // 底层库算法对象池
        faceEngineObjectPool = new GenericObjectPool(new FaceEngineFactory(sdkLibPath, appId, sdkKey, engineConfiguration), poolConfig);
        try {
            faceEngine = faceEngineObjectPool.borrowObject();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 确保精度
     *
     * @param value
     * @return
     */
    private int plusHundred (Float value) {
        BigDecimal target = new BigDecimal(value);
        BigDecimal hundred = new BigDecimal(100f);
        return target.multiply(hundred).intValue();

    }

    /**
     * 人脸检测
     *
     * @param imageInfo
     * @return
     */
    @Override
    public List<FaceInfo> detectFaces (ImageInfo imageInfo) {
        FaceEngine faceEngine = null;
        try {
            //获取引擎对象
            faceEngine = faceEngineObjectPool.borrowObject();

            //人脸检测得到人脸列表
            List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();

            //人脸检测
            faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
            return faceInfoList;
        } catch (Exception e) {
            logger.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineObjectPool.returnObject(faceEngine);
            }
        }
        return null;
    }


    /**
     * 提取年龄-性别
     *
     * @param imageInfo
     * @return
     */
    @Override
    public List<ProcessInfo> process (ImageInfo imageInfo) {
        FaceEngine faceEngine = null;
        try {
            //获取引擎对象
            faceEngine = faceEngineObjectPool.borrowObject();
            //人脸检测得到人脸列表
            List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
            //人脸检测
            faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
            int processResult = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, FunctionConfiguration.builder().supportAge(true).supportGender(true).build());
            List<ProcessInfo> processInfoList = Lists.newLinkedList();

            List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
            //性别提取
            int genderCode = faceEngine.getGender(genderInfoList);
            //年龄提取
            List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
            int ageCode = faceEngine.getAge(ageInfoList);
            for (int i = 0; i < genderInfoList.size(); i++) {
                ProcessInfo processInfo = new ProcessInfo();
                processInfo.setGender(genderInfoList.get(i).getGender());
                processInfo.setAge(ageInfoList.get(i).getAge());
                processInfoList.add(processInfo);
            }
            return processInfoList;

        } catch (Exception e) {
            logger.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineObjectPool.returnObject(faceEngine);
            }
        }

        return null;

    }

    /**
     * 人脸特征
     *
     * @param imageInfo
     * @return
     */
    @Override
    public byte[] extractFaceFeature (ImageInfo imageInfo) throws InterruptedException {

        FaceEngine faceEngine = null;
        try {
            //获取引擎对象
            faceEngine = faceEngineObjectPool.borrowObject();

            //人脸检测得到人脸列表
            List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();

            //人脸检测
            int i = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);

            if (CollectionUtil.isNotEmpty(faceInfoList)) {
                FaceFeature faceFeature = new FaceFeature();
                //提取人脸特征
                faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);

                return faceFeature.getFeatureData();
            }
        } catch (Exception e) {
            logger.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineObjectPool.returnObject(faceEngine);
            }

        }

        return null;
    }




    /**
     * 人脸比对
     *
     * @param groupId
     * @param faceFeature
     * @return 人脸组
     */
    @Override
    public List<FaceUserInfo> compareFaceFeature (byte[] faceFeature, Integer groupId) throws InterruptedException, ExecutionException {
        // 识别到的人脸列表
        List<FaceUserInfo> resultFaceInfoList = Lists.newLinkedList();
        // 创建人脸特征对象
        FaceFeature targetFaceFeature = new FaceFeature();
        targetFaceFeature.setFeatureData(faceFeature);
        // 根据分组拿人脸库,从数据库中取出人脸库
        List<FaceUserInfo> faceInfoList = userFaceInfoMapper.getUserFaceInfoByGroupId(groupId);
        // 分成50一组，多线程处理， 数据量大1000组
        List<List<FaceUserInfo>> faceUserInfoPartList = Lists.partition(faceInfoList, 50);
        // 多线程 需关心线程返回的结果 我们使用ExecutorCompletionService
        executorService = Executors.newFixedThreadPool(threadPoolSize);
        /*
         * 可能看到这里会很好奇 ExecutorCompletionService 实现原理，
         * 其实原理很简单，他在内部维护了一个阻塞队列，提交的任务，
         * 先执行完的先进入队列，所以你通过 poll 或 take 获得的肯定是最先执行完的任务结果。
         * 用处：多个线程，先执行完的进阻塞队列，然后可以按执行顺序获取结果
         * */
        CompletionService<List<FaceUserInfo>> completionService = new ExecutorCompletionService(executorService);
        for (List<FaceUserInfo> part : faceUserInfoPartList) {
            // 开始线程扫描人脸匹配度
            completionService.submit(new CompareFaceTask(part, targetFaceFeature));
        }
        // 获取线程任务参数
        for (List<FaceUserInfo> faceUserInfos : faceUserInfoPartList) {
            List<FaceUserInfo> faceUserInfoList = completionService.take().get();
            if (CollectionUtil.isNotEmpty(faceInfoList)) {
                resultFaceInfoList.addAll(faceUserInfoList);
            }
        }
        // 从大到小排序
        resultFaceInfoList.sort((h1, h2) -> h2.getSimilarValue().compareTo(h1.getSimilarValue()));
        // 关闭释放
        executorService.shutdown();
        return resultFaceInfoList;
    }


    /**
     * 多线程跑人脸对比逻辑
     */
    private class CompareFaceTask implements Callable<List<FaceUserInfo>> {

        private final List<FaceUserInfo> faceUserInfoList;
        private final FaceFeature targetFaceFeature;


        public CompareFaceTask (List<FaceUserInfo> faceUserInfoList, FaceFeature targetFaceFeature) {
            this.faceUserInfoList = faceUserInfoList;
            this.targetFaceFeature = targetFaceFeature;
        }

        @Override
        public List<FaceUserInfo> call () throws Exception {
            FaceEngine faceEngine = null;
            //识别到的人脸列表
            List<FaceUserInfo> resultFaceInfoList = Lists.newLinkedList();
            try {
                faceEngine = faceEngineObjectPool.borrowObject();
                for (FaceUserInfo faceUserInfo : faceUserInfoList) {
                    FaceFeature sourceFaceFeature = new FaceFeature();
                    // 设置人脸特征
                    sourceFaceFeature.setFeatureData(faceUserInfo.getFaceFeature());
                    FaceSimilar faceSimilar = new FaceSimilar();
                    faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
                    //获取相似值
                    Integer similarValue = plusHundred(faceSimilar.getScore());
                    //相似值大于配置预期，加入到识别到人脸的列表
                    if (similarValue > passRate) {
                        FaceUserInfo info = new FaceUserInfo();
                        info.setName(faceUserInfo.getName());
                        info.setFaceId(faceUserInfo.getFaceId());
                        info.setSimilarValue(similarValue);
                        resultFaceInfoList.add(info);
                    }
                }
            } catch (Exception e) {
                logger.error("", e);
            } finally {
                if (faceEngine != null) {
                    faceEngineObjectPool.returnObject(faceEngine);
                }
            }
            return resultFaceInfoList;
        }

    }
}
